package bmaso.purest.mock;

import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import com.mockobjects.servlet.MockJspWriter;

public class MyMockJspWriterWithVerifier extends MockJspWriter {
	private Runnable verifier;
	
	public Runnable getVerifier() {
		return verifier;
	}
	
	public void setVerifier(Runnable verifier) {
		this.verifier = verifier;
	}
	
	@Override
	public void verify() {
		if(verifier != null) {
			injectVerifierValue(verifier, getWriterContents());
			verifier.run();
		} else {
			super.verify();
		}
	}
	
	@Override
	public void setExpectedData(String data) {
		super.setExpectedData(data);
	}
	
	@Override
	public void flush() {
		//...no-op...
	}
	
	private String getWriterContents() {
		//...very ugly -- extract writer contents from private
		//   parent-class field "stringWriter" of type StringWriter
		//   using reflection. Ick, but the only way to get this
		//   data and s successfully re-use MockJspWriter as a
		//   parent class...
		try {
			StringWriter parentFieldValue = getParentWriter();
			return parentFieldValue.toString();
		} catch(NoSuchFieldException nsfe) {
			//...shouldn't ever happen...
			throw new Error(nsfe);
		} catch(IllegalAccessException iae) {
			//...shouldn't ever happen...
			throw new Error(iae);
		}
	}

	private StringWriter getParentWriter() throws NoSuchFieldException,
			IllegalAccessException {
		Field field = MockJspWriter.class.getDeclaredField("stringWriter");
		field.setAccessible(true);
		StringWriter parentFieldValue = (StringWriter)field.get(this);			
		field.setAccessible(false);
		return parentFieldValue;
	}
	
	private void injectVerifierValue(Object verifier, String value) {
		Method[] methods = verifier.getClass().getMethods();
		for(Method method : methods) {
			VerifierValue v = method.getAnnotation(VerifierValue.class);
			if(v != null) {
				//...found a method with the @VerifierValue annotation. Try
				//   to invoke it with single String argument...
				try {
					method.setAccessible(true);
					method.invoke(verifier, value);
				} catch(InvocationTargetException ite){
					throw new RuntimeException(ite);
				} catch(IllegalAccessException iae) {
					//...shouldn't happen since Method was retrieved using
					//   getMethods(), which should only return methods visible
					//   from this class...
					throw new Error(iae);
				}
			}
		}
	}
}
